package sanitycheck;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Random;

import pipeline.EdgeDetection;
import pipeline.FinalPrediction;
import setting.BioNLPSettings;
import def.AbstractText;
import def.ArgType;
import def.Argument;
import def.BioNLPDocument;
import def.Event;
import def.Protein;
import def.TSpan;
import exp.Count;

public class SolutionEnumeration2 {

	ArrayList<ArrayList<Event>> eventCandidates = null, proteinCandidates = null;
	
	public int maxFoundNum = 0;
	public int all = 0;
	/**
	 * 
	 * @param doc
	 * @param triggers
	 */
	public void enumerate(EdgeDetection ed, BioNLPDocument doc, int lineno, HashSet<Event> rawEvents) {
		//		terminate = false;
		document = doc;
		truthEvents = new ArrayList<Event>();
		text = (AbstractText) document.text;
		line = lineno;
		
		// prepare the true events in the current line
		for (Event e:  doc.events.values()){
			if (text.getSentenceIdByPos(e.tspan.startIdx)!=line) 
				continue;
			truthEvents.add(e);
			System.err.println("[TRUEEVENTS] "+doc.fileId+"@"+line+"  "+e);
		}
		all = truthEvents.size();
		if (truthEvents.size() == 0)
			// because truthevents = {} then it doesn't matter to recall
			return ;

		// 1. prepare all possible events
		eventCandidates = new ArrayList<ArrayList<Event>>();
		proteinCandidates = new ArrayList<ArrayList<Event>>();
		
		for (Event e: rawEvents){
			System.err.println("[RAWEVENTS] "+doc.fileId+"@"+line+"  "+e);
		}
		
		for (Protein p:doc.proteins){
			System.err.println("[PROTEIN] "+doc.fileId+"@"+line+" "+p);
		}
		
		generateCandidates(ed, rawEvents, proteinCandidates, eventCandidates);
		System.err.println("[SolEnu2]generating events with "+eventCandidates.size()+" E tspans; "+proteinCandidates.size() +" P tspans" );
		for (ArrayList<Event> el: eventCandidates){
			for (int i = 0; i < el.size(); i++){
				System.err.println("[SolEnu2]generating events with  E tspans; "+i+"/"+el.size()+"@@"+el.get(i)/*+proteinCandidates.size() +" P tspans" */);
			}
		}

		for (ArrayList<Event> el: proteinCandidates){
			for (int i = 0; i < el.size(); i++){
			System.err.println("[SolEnu2]generating events with  P tspans; "+i+"/"+el.size()+"@@"+el.get(i)+" "+el.size()/*+proteinCandidates.size() +" P tspans" */);
			}
		}
		// start enumeration
		// the last argument HashSet is for filtering
		// even if the iteration is going to the end (i.e. candidates.size()), still need to check with the filterset,
		// if it's nonempty, start from the beginning, build an upper layer of events, although we do need to eval at all possible solutions.
		// meaning that evaluate w/ and w/o upper layers
//		if (proteinCandidates.size()!=0)
			generateSolution( 0, 0, new ArrayList<Event>(),true);





		//				ArrayList<ArrayList<Event>> solutions = new ArrayList<ArrayList<Event>>();
		//				System.err.println("Generating events with "+potentialParents.size()+"  "+bindings.size()+"   "+onlyChildren.size()+" "+proteins.size());
		//				System.err.println("parents:: "+potentialParents);
		//				System.err.println("bindings::"+bindings);
		//				System.err.println("children::"+onlyChildren);
		//				System.err.println("proteins::"+proteins);
		//				generateEvents(potentialParents, bindings, onlyChildren, proteins, 0,0, 0, 0, new ArrayList<Event>(), solutions);



	}
	public int level = 0;
	
	public boolean changed = false;
	private void generateSolution(/*ArrayList<ArrayList<Event>> candidates, */
			int i, int j, ArrayList<Event> current, boolean baseLevel) {
		// start enumeration
		// the last argument HashSet is for filtering
		// even if the iteration is going to the end (i.e. candidates.size()), still need to check with the filterset,
		// if it's nonempty, start from the beginning, build an upper layer of events, although we do need to eval at all possible solutions.
		// meaning that evaluate w/ and w/o upper layers
		if (terminate)
			return;
		
		
		ArrayList<ArrayList<Event>> candidates = baseLevel?proteinCandidates: eventCandidates;
		if (candidates.size()==0)
			return;
		ArrayList<Event> list = candidates.get(i);
		
		/*else*/{

			//		for (int j = 0; j < list.size(); j++){
//			if (list.get(j).tspan.text.equals("induced") &&!baseLevel){
//				System.err.println("[ADHOC_DEBUG] induced is looking for an argument! in baselevel="+baseLevel);
//				System.err.println("[ADHOC_DEBUG] induced is looking for an argument! current = "+current);
//			}
			while (true){
//				System.err.println("[DELETEME] i="+i+", j="+j+", baselevel="+baseLevel +"  level="+level);
//				if (j > list.size()-1){
//					System.err.println("[DELETEME] i="+i+", j="+j+", baselevel="+baseLevel);
//				}
				if (list.size() == 0){
					if (i+1 > candidates.size() -1){
						// next level is not baseLevel for sure
						if (changed){

							//							System.err.println("[BASELEVEL=FALSE] !!");
							level++;
							if (level<BioNLPSettings.getIntValue("MAX_LEVEL")){
								
								boolean prevChangeState = changed;
								changed = false;
								generateSolution(0, 0, current, false);
								changed = prevChangeState;
								
							}
							else{	
								//							break;
							}
							level--;
						}else{
							//						System.err.println("[DELETEME] nothing changed; continue");
						}
					}
					else{
						generateSolution(i+1, 0, current, baseLevel);
					}
					break;
				}
				
				if (isCompatible(list.get(j), current, baseLevel)){
					
//					eval(current);
//					System.err.println("[DELETEME] i="+i+", j="+j+", baselevel="+baseLevel +" arg=0"+"  level="+level);
					if (j+1 > list.size()-1){
						if (i+1 > candidates.size()-1){
							// next level is not baseLevel for sure
							if (changed){
								
								//							System.err.println("[BASELEVEL=FALSE] !!");
								
								if (level<BioNLPSettings.getIntValue("MAX_LEVEL")){
									level++;
									boolean prevChangeState = changed;
									changed = false;
									generateSolution(0, 0, current, false);
									changed = prevChangeState;
									level--;
								}
								else{	
//									break;
								}
							}else{
//								System.err.println("[DELETEME] nothing changed; continue");
							}
						}
						else{
							generateSolution(i+1, 0, current, baseLevel);
						}
					}
					else{
						generateSolution(i, j+1, current, baseLevel);
					}
					
					
					int addedEvents = 0;
					boolean prevChangeState = changed;
//					System.err.println("[DELETEME] i="+i+", j="+j+", baselevel="+baseLevel +" arg=1"+"  level="+level);
					if (baseLevel){
						current.add(list.get(j));
						changed = true;
						addedEvents = 1;
					}
					else{
//						boolean print = false;
//						if (this.maxFoundNum >= 2&&current.size() == 2) {
////							System.err.println("[DEBUG_2_2] "+current+ +"\t"+ isCompatible(list.get(j), current, baseLevel));
//							if (current.get(0).tspan.text.equals("induction")&&current.get(1).tspan.text.equals("induction") ){
//								if (list.get(j).tspan.text.equals("occurs")){
//									if (list.get(j).args.size() == 1){
//										print = true;
//									}
//									System.err.println("[DEBUG] compatible = "+ isCompatible(list.get(j), current, baseLevel)+" "+ list.get(j));
//								}
//							}
//						}
						//					???when list.get(j) has another event to hook up
						addedEvents = hookup(list.get(j), current);
						changed = true;
//						if (print){
//							System.err.println("[DEBUG_2_2_added] "+current);
//						}
					}
					eval(current);
					
					// final prediction
					FinalPrediction.buildFeatures(current, document, line, truthEvents);
					Random r = new Random(1829);
					float ratio = (float)100 / FinalPrediction.examples.size(); 
					for (int exampleIdx = 0; exampleIdx < FinalPrediction.examples.size(); exampleIdx++){
						if (FinalPrediction.scores.get(exampleIdx) == 0 &&r.nextFloat() > ratio){
							continue;
						}
						System.out.print("[EXAMPLE]"+FinalPrediction.scores.get(exampleIdx));
						Hashtable<String, Integer> example = FinalPrediction.examples.get(exampleIdx);
						for (String key: example.keySet()){
							System.out.print(" "+key+":"+example.get(key));
						}
						System.out.println();
					}
					
					
					
					if (j+1 > list.size()-1){
						if (i+1 > candidates.size()-1){
							// next level is not baseLevel for sure
							if (changed){
//								prevChangeState = changed;
								changed = false;
								//							System.err.println("[BASELEVEL=FALSE] !!");
								level++;
								if (level<BioNLPSettings.getIntValue("MAX_LEVEL")){
									generateSolution(0, 0, current, false);
								}
								level--;
//								changed = prevChangeState;
//								break;
								
							}else{
								// never gets in here.
								System.err.println("[DELETEME] nothing changed; continue");
							}
						}
						else{
							generateSolution(i+1, 0, current, baseLevel);
						}
					}
					else{
						generateSolution(i, j+1, current, baseLevel);
					}
					if (baseLevel){
						current.remove(current.size()-1);
					}
					else{
						for (int k = 0; k < addedEvents; k++){
							current.remove(current.size()-1);
						}
						//					detach(current);
					}
					changed = prevChangeState;
					break;
				}
				else{
					j++;
					if (j > list.size()-1){
						if (i+1 > candidates.size()-1){
							// next level is not baseLevel for sure
							if (changed){
								changed = false;
								//							System.err.println("[BASELEVEL=FALSE] !!");
								level++;
								if (level<BioNLPSettings.getIntValue("MAX_LEVEL")){
									generateSolution(0, 0, current, false);
								}
								level--;
								changed = true;
								break;
							}
							else{
//								System.err.println("[DELETEME] nothing changed; break;");
								break;
							}
						}
						else{
//							generateSolution(i+1, 0, current, baseLevel);
							i++;
							list = candidates.get(i);
							j = 0;
						}
					}
				}
			}
			
			
		}
		//		}
	}
	private void detach(ArrayList<Event> current) {
		// TODO Auto-generated method stub

	}

	private int hookup(Event event, ArrayList<Event> current) {
		Hashtable<TSpan, ArrayList<Event>> toAdd = new Hashtable<TSpan, ArrayList<Event>>();
		for (Argument arg: event.args){ 
			if (arg instanceof Event){
				ArrayList<Event> list = new ArrayList<Event>();
				toAdd.put(arg.tspan, list);
				for (Event e2: current){
					if (e2.tspan.equals(arg.tspan)){
						list.add(e2);
					}
				}
				if (list.size() == 0){
					System.err.println("[HOOKUP] didn't find an event to hook up");
//					System.err.println("[HOOKUP_DEBUG] arg event: "+ arg.tspan);
//					for (Event e2: current){
//						System.err.println("[HOOKUP_DEBUG] current events: "+e2.tspan);
//					}
				}
			}
		}
		int res = 0;
		if (toAdd.size() == 1){
			TSpan tspan = toAdd.keys().nextElement();
			ArrayList<Event> list = toAdd.get(tspan);
			for (Event e2: list){
				Event newEvent = new Event();
				newEvent.tspan = event.tspan;
				for (int i = 0; i < event.args.size(); i++){
					if (event.args.get(i).tspan.equals(tspan)){
						newEvent.args.add(e2);
					}
					else{
						newEvent.args.add(event.args.get(i));
					}
					newEvent.types.add(event.types.get(i));
				}
				current.add(newEvent);
			}
			res = list.size();
		}
		else if (toAdd.size() == 2){
			Enumeration<TSpan> enu =  toAdd.keys();
			TSpan tspan =enu.nextElement(),tspan2 = enu.nextElement();
			ArrayList<Event> list = toAdd.get(tspan), list2 = toAdd.get(tspan2);
			for (Event a1: list){
				for (Event a2: list2){
					Event newEvent = new Event();
					newEvent.tspan = event.tspan;
					for (int i = 0; i < event.args.size(); i++){
						if (event.args.get(i).tspan.equals(tspan)){
							newEvent.args.add(a1);
						}
						else if (event.args.get(i).tspan.equals(tspan2)){
							newEvent.args.add(a2);
						}
						else{
							newEvent.args.add(event.args.get(i));
						}
						newEvent.types.add(event.types.get(i));
					}
					current.add(newEvent);
				}
			}
			res = list.size() * list2.size();
		}
		else{
			System.err.println("[HOOKUP] toAdd size is wrong "+toAdd.size());
			System.err.println("[HOOKUP_DEBUG] arg event: "+ event.tspan);
			for (Event e2: current){
				System.err.println("[HOOKUP_DEBUG] current events: "+e2.tspan);
			}
		}
		return res;
	}



	private void eval(ArrayList<Event> current) {
		// with truthEvents
		debugCounter++;
		if (debugCounter % 1000000 == 0)
			System.err.println("[DEBUGCOUNTER]"+debugCounter+" ...");
		if (debugCounter > 1e7)
			terminate = true;
		int fp = current.size();
		
		  
	
		
		Count c = new Count();
		//		HashSet<Event> tpInPred = new HashSet<Event>(), tpInTruth = new HashSet<Event>();
		//		HashSet<Event> truths = new HashSet<Event>();
		//		HashSet<String> truthString = new HashSet<String>();
		//		for (Event e:  document.events.values()){
		//			if (text.getSentenceIdByPos(e.tspan.startIdx)!=line) continue;
		//			String str = e.toString();
		//			str = str.substring(str.indexOf("\t"));
		//			if (!truthString.contains(str)){
		////				truths.add(e);
		//				truthString.add(str);
		//			}
		//		}
		for (Event e:truthEvents){
			c.all++;
			//			Event found = null;
			for (Event pe: current){
				if (match(pe, e)){
					//				if (tpInPred.contains(pe)) continue;
					fp--;
					c.num++;
				}
			}
		}
		c.fp += fp;
//		System.out.println("[EVAL] "+document.fileId + "@"+ line+"  "+c.num+" "+c.fp+" "+c.all+" "+current);
		
		
		if (c.num == c.all && c.fp == 0){
			for (int i = 0; i < current.size(); i++){
				System.err.println("[EVENT_HIT] @"+i+" "+current.get(i));
			}
			for (int i = 0; i < truthEvents.size(); i++){
				System.err.println("[EVENT_HIT_TRUTH] @"+i+" "+truthEvents.get(i));
			}
			terminate = true;
		}
		if (c.num > maxFoundNum && c.fp == 0){
			maxFoundNum = c.num;
			System.err.println("[EVENT_HIT] found "+maxFoundNum+" "+current);
		}
	}

	private boolean match(Event pe, Event e){
		boolean res = true;
		if (pe.tspan.matchTruth(e.tspan, document)){
			//			System.err.println("[EVAL TRUTH]"+e);
			//			System.err.println("[EVAL PRED]"+pe);
			// FIXME consider Site arguments TODO
			// NOTE: forget about the event types for now
			int match = -1;
			if (e.args.size() == pe.args.size()){
				match = 0;
				for (int j = 0; j < e.args.size(); j++){
					for (int k = 0; k < pe.args.size(); k++){
						if (e.args.get(j) instanceof Protein){
							if (pe.args.get(k) instanceof Protein){
								if (e.args.get(j).equals(pe.args.get(k))&&e.types.get(j).match(pe.types.get(k))){
									match++;		
								}
							}
						}
						else if (e.args.get(j) instanceof Event){
							if(pe.args.get(k) instanceof Event){
								if (match((Event)pe.args.get(k), (Event)e.args.get(j)) &&e.types.get(j).match(pe.types.get(k))){
									match++;
								}
							}
						}
					}
				}
			}
			else{
				match = -1;
			}
			//			System.err.println("[EVAL EQUAL] "+(match ==  e.args.size())+" " +  match);
			if (match ==  e.args.size()){
				//				if (found!=null){
				//					//					System.err.println("already matched this "+e+" ---with--- "+found+"\n***but "+pe);
				//					break;
				//				}
				//				tpInPred.add(pe);
				//				tpInTruth.add(e);
				//				c.num ++;
				//				fp -- ;
				//				found =pe;
				res = true;
				//						break;
			}
			else{
				res = false;
			}
		}
		else{
			res = false;
		}
		return res;
	}

	private boolean isCompatible(Event e, ArrayList<Event> events, boolean baseLevel){

		boolean valid =  true;
		int occurrence = 0;
		ArrayList<Event> sameTSpans = new ArrayList<Event>();
		for (Event e2: events){
			if (e.tspan.equals(e2.tspan)){
				//				valid = false;
				sameTSpans.add(e2);


				boolean hasEvent = false;
				for (Argument arg: e2.args){
					if (arg instanceof Event){
						hasEvent = true;
						break;
					}
				}
				if (!hasEvent)
					occurrence++;
				else if (!baseLevel && hasEvent){
					occurrence++;
				}
				// can't have more than

				if (baseLevel && occurrence >= 2){
					// if e is a base level event, then we can't have more than 2 same tspans
					valid = false;
					break;
				}
				else if (!baseLevel && occurrence >= 1){
					// if e is not a base level event, then we can't have the same tspan in two different levels.
					// NOTE: it avoids the loop
					valid = false;
					break;
				}
			}
		}
		if (!valid)
			return valid;

		if (baseLevel){
			// can't have argument intersection larger than two elements
			for (int i = 0; i < sameTSpans.size(); i++){
				Event e2 = sameTSpans.get(i);
				int hit = 0;
				for (Argument arg: e.args){
					if (e2.args.contains(arg)){
						hit++;
					}
				}
				if (hit == e2.args.size() || hit == e.args.size() || hit >= 2)
				{
					valid = false;
					break;
				}
			}
		}
		else {
			// must have all argument tspan already in current events
			for (Argument arg: e.args){
				
				if (arg instanceof Event){
					boolean hit = false;
					for (Event e2: events){
						if (arg.tspan.equals(e2.tspan)){
							hit = true;
							break;
						}
					}
					if (!hit){
						valid = false; 
						break;
					}
				}
				
			}
		}

		return valid;
	}

	private void generateCandidates(EdgeDetection ed,
			HashSet<Event> rawEvents, ArrayList<ArrayList<Event>> proteinCandidates, ArrayList<ArrayList<Event>> eventCandidates) {

		//		ArrayList<ArrayList<Event>> candidates = new ArrayList<ArrayList<Event>>();

		for (Event e: rawEvents){
			// results  for events, results2 for proteins
			ArrayList<Event> results = new ArrayList<Event>(), results2 = new ArrayList<Event>();
			boolean isBinding = false, isRegulate = false;
			if (ed.td.parentDictionary.contains(e.tspan.text)){
				isRegulate = true;
			}
			if (ed.td.bindingDictionary.contains(e.tspan.text)){
				isBinding = true;
			}
			// split e
			if (e.args.size() == 0){
				// done
			}
			else if (e.args.size() == 1){
				if (e.args.get(0) instanceof Protein){
					Event newEvent = new Event();
					newEvent.tspan = e.tspan;
					newEvent.args.add(e.args.get(0));
					newEvent.types.add(ArgType.Theme);
					results2.add(newEvent);
					proteinCandidates.add(results2);	
				}
				else if (e.args.get(0) instanceof Event){
					if (isRegulate){
						Event newEvent = new Event();
						newEvent.tspan = e.tspan;
						newEvent.args.add(e.args.get(0));
						newEvent.types.add(ArgType.Theme);
						results.add(newEvent);
						eventCandidates.add(results);
					}
				}
			}else if (e.args.size() >= 2){
				int numEventArgs = 0, numProteinArgs = 0;
				ArrayList<Event> eventArgs = new ArrayList<Event>();
				ArrayList<Protein> proteinArgs = new ArrayList<Protein>();
				for (Argument arg: e.args){
					if (arg instanceof Event){
						numEventArgs ++;
						eventArgs.add((Event)arg);
					}
					else if (arg instanceof Protein){
						numProteinArgs ++;
						proteinArgs.add((Protein)arg);
					}
				}
				System.err.println("**********EVENT_ARGUMENTS************");
				System.err.println("[EVENT] "+e);
				for (Protein p: proteinArgs){
					System.err.println("[EVENT_PROTEIN] "+p);
				}
				for (Event ee: eventArgs){
					System.err.println("[EVENT_EVENT] "+ee);
				}
				System.err.println("**********END_EVENT_ARGUMENTS************");
				
				if (isRegulate){
					// deal with event args first
					if (numEventArgs == 1){
						// only argument as theme
						Event newEvent = new Event();
						newEvent.tspan = e.tspan;
						newEvent.args.add(eventArgs.get(0));
						newEvent.types.add(ArgType.Theme);
						results.add(newEvent);

						// add a protein as cause
						for (int i  = 0; i < numProteinArgs; i++){
							newEvent = new Event();
							newEvent.tspan = e.tspan;
							newEvent.args.add(eventArgs.get(0));
							newEvent.types.add(ArgType.Theme);
							newEvent.args.add(proteinArgs.get(i));
							newEvent.types.add(ArgType.Cause);
							results.add(newEvent);
						}

						// add the event as a cause and some protein as the theme
						for (int i  = 0; i < numProteinArgs; i++){
							newEvent = new Event();
							newEvent.tspan = e.tspan;
							newEvent.args.add(eventArgs.get(0));
							newEvent.types.add(ArgType.Cause);
							newEvent.args.add(proteinArgs.get(i));
							newEvent.types.add(ArgType.Theme);
							results.add(newEvent);
						}
					}
					else if (numEventArgs >= 2){
						// choose two events as arguments
						for (int i = 0; i < numEventArgs; i++){
							// first only argument
							Event newEvent = new Event();
							newEvent.tspan = e.tspan;
							newEvent.args.add(eventArgs.get(i));
							newEvent.types.add(ArgType.Theme);
							results.add(newEvent);

							// one event arg and one protein arg
							for (int j = 0; j < numProteinArgs; j++){
								newEvent = new Event();
								newEvent.tspan = e.tspan;
								newEvent.args.add(eventArgs.get(i));
								newEvent.types.add(ArgType.Theme);
								newEvent.args.add(proteinArgs.get(j));
								newEvent.types.add(ArgType.Cause);
								results.add(newEvent);

								newEvent = new Event();
								newEvent.tspan = e.tspan;
								newEvent.args.add(eventArgs.get(i));
								newEvent.types.add(ArgType.Cause);
								newEvent.args.add(proteinArgs.get(j));
								newEvent.types.add(ArgType.Theme);
								results.add(newEvent);
							}

							// both event args
							for (int j = i + 1; j < numEventArgs ; j++){
								// cause theme
								newEvent = new Event();
								newEvent.tspan = e.tspan;
								newEvent.args.add(eventArgs.get(i));
								newEvent.types.add(ArgType.Cause);
								newEvent.args.add(eventArgs.get(j));
								newEvent.types.add(ArgType.Theme);
								results.add(newEvent);

								// theme cause
								newEvent = new Event();
								newEvent.tspan = e.tspan;
								newEvent.args.add(eventArgs.get(i));
								newEvent.types.add(ArgType.Theme);
								newEvent.args.add(eventArgs.get(j));
								newEvent.types.add(ArgType.Cause);
								results.add(newEvent);

								// theme - theme: impossible
								/*newEvent = new Event();
							newEvent.tspan = e.tspan;
							newEvent.args.add(eventArgs.get(i));
							newEvent.types.add(ArgType.Theme);
							newEvent.args.add(eventArgs.get(j));
							newEvent.types.add(ArgType.Theme);
							results.add(newEvent);*/
							}
						}
					}
					

					//  add theme/cause proteins
					if (numProteinArgs >=2){
						for (int i = 0; i < numProteinArgs; i++){
							for (int j = i + 1; j < numProteinArgs ; j++){
								// cause/ theme
								Event newEvent = new Event();
								newEvent.tspan = e.tspan;
								newEvent.args.add(proteinArgs.get(i));
								newEvent.types.add(ArgType.Cause);
								newEvent.args.add(proteinArgs.get(j));
								newEvent.types.add(ArgType.Theme);
								results2.add(newEvent);
								//theme/cause
								newEvent = new Event();
								newEvent.tspan = e.tspan;
								newEvent.args.add(proteinArgs.get(i));
								newEvent.types.add(ArgType.Theme);
								newEvent.args.add(proteinArgs.get(j));
								newEvent.types.add(ArgType.Cause);
								results2.add(newEvent);
							}
						}
					}					
				}

				// protein args
				// 1. choose 1 protein
				for (int i = 0; i < numProteinArgs; i++){
					Event newEvent = new Event();
					newEvent.tspan = e.tspan;
					newEvent.args.add(proteinArgs.get(i));
					newEvent.types.add(ArgType.Theme);
					results2.add(newEvent);
				}
				// 2. choose two proteins as themes if binding
				// add MAX_CHOOSE themes
				// FIXME simply assume MAX_CHOOSE = 2

				if (isBinding && numProteinArgs >=2){
					for (int i = 0; i < numProteinArgs; i++){
						for (int j = i + 1; j < numProteinArgs ; j++){
							// theme/ theme
							Event newEvent = new Event();
							newEvent.tspan = e.tspan;
							newEvent.args.add(proteinArgs.get(i));
							newEvent.types.add(ArgType.Theme);
							newEvent.args.add(proteinArgs.get(j));
							newEvent.types.add(ArgType.Theme);
							results2.add(newEvent);
						}
					}
				}
				if (results.size()>0)
					eventCandidates.add(results);
				if (results2.size()>0)
					proteinCandidates.add(results2);
			}
			
		}

		//		return candidates;
	}
	public ArrayList<Event>  truthEvents = null;
	public AbstractText text = null;
	public BioNLPDocument document = null;
	public int maxLength = 0;
	public int line = -1;
	public int debugCounter = 0;
	public boolean terminate = false;
	

	private boolean checkLength(ArrayList<Event> events){
		int length = 0;
		for (Event e: events){
			length += e.args.size();
		}
		if (length > maxLength)
			return false;
		else 
			return true;
	}

	


	/**
	 * return false if events are invalid, e.g. containing loops
	 * @param events
	 * @return
	 */
	public boolean check(ArrayList<Event> events){
		boolean valid = true;
		HashSet<Event> visited = new HashSet<Event>();
		for (Event e: events){
			if (visited.contains(e)){
				valid = false;
				break;
			}
			if (!checkEvent(e, new HashSet<Event>())){
				valid = false;
				break;
			}
			visited.add(e);
		}
		return valid;
	}

	/**
	 * return false if addon makes the events invalid
	 * @param events: must be valid
	 * @param addon
	 * @return
	 */
	public boolean check(ArrayList<Event> events, Event addon){
		boolean valid = true;
		return valid;
	}


	public boolean checkEvent(Event event, HashSet<Event> visited){
		//		System.err.println("checking if already checked "+e );
		boolean valid = true;
		for (Event e: visited){
			if (event == e){
				// containing loop
				return false;
			}
		}
		visited.add(event);
		boolean hasProteinAsTheme = false;
		if (event.args.size() == 0){
			valid = false;
		}
		else if (event.args.size() ==1 ){
			if (event.types.get(0) != ArgType.Theme)
				valid = false;
			else{
				valid = checkArg(event.args.get(0), visited);
			}
		}else if (event.args.size() == 2){
			if ((event.types.get(0) ==  ArgType.Theme && event.types.get(1) == ArgType.Cause)
					||(event.types.get(0) ==  ArgType.Cause && event.types.get(1) == ArgType.Theme)){
				if (event.args.get(1) instanceof Event){
					visited.add((Event)event.args.get(1));
				}
				if (!checkArg(event.args.get(0), visited)){
					valid = false;
				}
				if (event.args.get(1) instanceof Event){
					visited.remove((Event)event.args.get(1));
				}
				if (event.args.get(0) instanceof Event){
					visited.add((Event)event.args.get(0));
				}
				if (!checkArg(event.args.get(1), visited)){
					valid = false;
				}
				if (event.args.get(0) instanceof Event){
					visited.remove((Event)event.args.get(0));
				}	
			}
			else{
				valid = false;
			}
			/*
			ArrayList<Integer> toRemove = new ArrayList<Integer>();
			for (int i = 0; i < event.args.size(); i++){
				if (event.args.get(i) instanceof Event){
					boolean flag = checkEvent((Event)event.args.get(i), visited);
					if (!flag){
						valid = flag;
						break;
					}
				}
				else if (event.args.get(i) instanceof Protein){
					if (event.types.get(i)== ArgType.Theme){
						hasProteinAsTheme = true;
					}
				}
			}
			for (int i = 0; i < toRemove.size(); i++){
				event.args.remove(toRemove.get(toRemove.size()-1-i));
				event.types.remove(toRemove.get(toRemove.size()-1-i));
			}
			if (event.args.size() > 0)
				valid = true;
			 */
		}
		else{
			// should all be proteins
			boolean allProteins = true;
			for (int i = 0; i < event.args.size(); i++){
				Argument arg = event.args.get(i);
				if (arg instanceof Event){
					allProteins = false;
					break;
				}
				else{
					if (event.types.get(i)!=ArgType.Theme){
						// has to be all Themes
						allProteins = false;
						break;
					}
				}
			}
			if (!allProteins)
				valid = allProteins;
		}
		return valid;
	}

	public boolean checkEventLoop(Event event, HashSet<Event> visited){
		//		System.err.println("checking if already checked "+e );
		boolean valid = true;
		for (Event e: visited){
			if (event == e){
				// containing loop
				return false;
			}
		}
		visited.add(event);
		boolean hasProteinAsTheme = false;
		if (event.args.size() ==1 ){
			if (event.types.get(0) != ArgType.Theme)
				valid = false;
			else{
				if (event.args.get(0) instanceof Event){
					valid = checkEventLoop((Event)event.args.get(0), visited);
				}
			}
		}else if (event.args.size() == 2){
			if ((event.types.get(0) ==  ArgType.Theme && event.types.get(1) == ArgType.Cause)
					||(event.types.get(0) ==  ArgType.Cause && event.types.get(1) == ArgType.Theme)){
				if (event.args.get(0) instanceof Event){
					if (event.args.get(1) instanceof Event){
						visited.add((Event)event.args.get(1));
					}
					if (!checkEventLoop((Event)event.args.get(0), visited)){
						valid = false;
					}
					if (event.args.get(1) instanceof Event){
						visited.remove((Event)event.args.get(1));
					}
				}
				if (valid){
					if (event.args.get(1) instanceof Event){
						if (event.args.get(0) instanceof Event){
							visited.add((Event)event.args.get(0));
						}
						if (!checkEventLoop((Event)event.args.get(1), visited)){
							valid = false;
						}
						if (event.args.get(0) instanceof Event){
							visited.remove((Event)event.args.get(0));
						}	
					}
				}
			}
			else{
				valid = false;
			}
		}
		return valid;
	}

	public boolean checkArg(Argument arg, HashSet<Event> visited){
		if (arg instanceof Event){
			return checkEvent((Event)arg, visited);
		}
		else{
			return true;
		}
	}
}
